"""
iOS 自动化驱动模块

作为iOS自动化框架的主要入口点
基于facebook-wda框架，使用go-ios进行设备通信
"""
import os
import time

import tempfile
import subprocess
from typing import Dict, Optional
from lib.logger import LoggerManager
from cv import imgcv
from lib.ios_utils import GoIOS, GoIOSError, Simulator
from lib.ios_appium_webview import IOSAppiumWebView
from lib.pic_base import PicBase
# 尝试导入必要的库
try:
    import wda
    from wda import Client, list_devices
except ImportError:
    raise ImportError("请安装必要的依赖: pip install facebook-wda")

# 配置日志
logger = LoggerManager.get_logger(__name__, write_to_file=False)


class ConnectionErr(Exception):
    """框架基础异常类"""

    def __init__(self, message: str, cause: Exception = None):
        self.message = message
        self.cause = cause
        super().__init__(self.message)


class USBClient(Client):
    """ connect device through unix:/var/run/usbmuxd """

    def __init__(self, udid: str = "", port: int = 8100, wda_bundle_id=None):
        if not udid:
            infos = [info for info in list_devices() if info.connection_type == 'USB']
            if len(infos) == 0:
                raise RuntimeError("no device connected")
            elif len(infos) >= 2:
                raise RuntimeError("more then one device connected")
            udid = infos[0].serial

        super().__init__(url=f"http+usbmux://{udid}:{port}")
        if self.is_ready():
            return
        GoIOS.run_wda(wda_bundle_id, wda_bundle_id, udid=udid)
        if not self.wait_ready(timeout=20):
            raise RuntimeError("wda xctest launched but check failed")


class IOSDriver(PicBase):
    """
    iOS自动化驱动类，基于facebook-wda实现
    """

    def __init__(self, device_id: Optional[str] = None,
                 device_name: Optional[str] = None,
                 wda_bundle_id: str = "com.yonyou.wda.test02.xctrunner",
                 app_bundle_id: str = "",
                 local_port: int = 8100,
                 auto_start: bool = True,
                 use_usb: bool = True,
                 is_simulator: bool = False,
                 use_appium_webview: bool = True,
                 ):
        """
        初始化iOS自动化驱动
        
        Args:
            device_id: 设备ID (UDID)，如果为None则使用第一个可用设备
            device_name: 设备名称，如果device_id为None则使用device_name查找设备
            wda_bundle_id: WebDriverAgent的Bundle ID
            wda_port: 设备上WDA服务的端口
            local_port: 本地转发端口
            auto_start: 是否自动启动连接
            use_usb: 是否使用USB连接
            is_simulator: 是否为模拟器
        """
        self.wda: wda.USBClient

        self.device_id = device_id
        self.device_name = device_name
        self.wda_bundle_id = wda_bundle_id
        self.app_bundle_id = app_bundle_id
        self.local_port = local_port
        self.use_usb = use_usb
        self.is_simulator = is_simulator
        self.webview = None

        # 初始化wdapy实例
        self.screen_size = None

        # 检查go-ios是否安装
        self._check_go_ios()
        # 查询连接设备，如果有多个默认选择第一个
        devices = self._get_device_list()

        logger.info(f"获取的可用机型:{devices}")
        if len(devices_List := devices.get('deviceList')) > 0:
            if not self.device_id:
                if len(devices_List) >= 2:
                    logger.warning("查询到多个设备连接，默认选择第一个")
                self.device_id = devices_List[0]['Udid']
                self.max_version = devices_List[0]['ProductVersion'].split('.')[0]

        else:
            raise ConnectionErr(f"没有查询到设备连接")

        # 自动启动连接
        if auto_start:
            self.start()
        if use_appium_webview:
            self.start_appium_webview()

    def _check_go_ios(self):
        """
        检查go-ios是否安装
        
        Returns:
            bool: 是否安装
        """
        try:
            version = GoIOS.get_version()
            logger.info(f"检测到go-ios版本: {version}")
            return True
        except Exception as e:
            logger.error(f"go-ios未安装或无法运行: {e}")
            return False

    def start_appium_webview(self):
        self.webview = IOSAppiumWebView(
            device_id=self.device_id,
            bundle_id=self.app_bundle_id,
            wda_port=self.local_port,
            use_usb_client=self.use_usb
        )

    def _start_tunnel(self):
        """
        启动隧道
        
        Returns:
            bool: 是否成功
        """
        result = GoIOS.ios_tunnel_status(self.device_id)
        if all([isinstance(result, list), len(result) > 0, 'userspaceTun' in result[0]]):
            logger.info(f"隧道已存在: {result}")
            return True
        else:
            logger.error(f"启动隧道失败")
            return False

    def _start_wda(self):
        """
        启动WDA
        
        Returns:
            bool: 是否成功
        """
        try:
            if self.is_simulator:

                logger.info("模拟器WDA跳过启动步骤。默认用户已安装！")
                return True
            else:
                # 对于真机，使用GoIOS启动WDA
                self._wda_process = GoIOS.run_wda(
                    bundle_id=self.wda_bundle_id,
                    test_runner_bundle_id=self.wda_bundle_id,
                    xctestconfig="WebDriverAgentRunner.xctest",
                    udid=self.device_id
                )
                logger.info(f"run_wda成功:{self._wda_process}")

            # 等待WDA启动完成
            # time.sleep(5)
            return True
        except Exception as e:
            logger.error(f"启动WDA失败: {e}")
            return False

    def _get_device_info(self):
        """
        获取设备信息
        
        Returns:
            dict: 设备信息
        """
        try:
            if self.device_id:
                return GoIOS.get_device_info(self.device_id)
            return {}
        except GoIOSError as e:
            logger.error(f"获取设备信息失败: {e}")
            return {}

    def _get_device_list(self):
        """
        获取已连接的设备列表
        
        Returns:
            dict: 设备列表，格式为 {"deviceList": [{"udid": "xxx", "name": "xxx", ...}]}
        """
        try:
            device_list = []

            if self.is_simulator:
                # 获取模拟器列表
                devices = Simulator.list_devices()
                # 处理模拟器设备信息
                for sys_info, simulators in devices.get("devices", {}).items():
                    for device in simulators:
                        # 只添加已启动的模拟器
                        if device.get("state") == "Booted":
                            device_list.append({
                                "Udid": device.get("udid"),
                                "name": device.get("name"),
                                "type": "simulator",
                                "state": device.get("state"),
                                "runtime": sys_info
                            })
            else:
                # 获取真机设备列表
                devices = GoIOS.list_devices(details=True)
                device_list = devices.get("deviceList", [])

            return {"deviceList": device_list}
        except Exception as e:
            logger.error(f"获取设备列表失败: {e}")
            return {"deviceList": []}

    def _init_wda(self):
        """
        初始化wda客户端
        
        Returns:
            bool: 是否成功
        """
        try:
            # 构建WDA客户端URL
            if self.is_simulator:
                # 对于模拟器，直接使用localhost
                wda_url = f"http://localhost:{self.local_port}"
                self.wda = wda.Client(wda_url)
            else:
                # 对于真机，使用USB连接或者通过端口转发
                if self.use_usb:
                    self.wda = USBClient(self.device_id, port=self.local_port,wda_bundle_id=self.wda_bundle_id)
                    wda_url = f"http+usbmux://{self.device_id}:{self.local_port}"
                else:
                    wda_url = f"http://localhost:{self.local_port}"
                    self.wda = wda.Client(wda_url)
            setattr(self, "wda_url", wda_url)
            # 等待WDA连接就绪
            for _ in range(10):
                try:
                    status = self.wda.status()
                    logger.info(f"WDA客户端初始化成功:{status}")

                    # # 获取屏幕尺寸
                    # if self.is_simulator:
                    #     self.screen_size = self.wda.window_size()
                    # else:
                    #
                    #     self.screen_size = self._get_screen_size()
                    # logger.info(f"设备屏幕尺寸: {self.screen_size}")

                    return True
                except Exception as e:
                    logger.warning(f"等待WDA连接就绪: {e}")
                    time.sleep(1)

            logger.error("WDA客户端初始化超时")
            return False
        except Exception as e:
            logger.error(f"初始化WDA客户端失败: {e}")
            return False

    def start(self):
        """
        启动iOS自动化驱动，连接设备并初始化wda
        
        Returns:
            bool: 是否成功启动
        """
        try:
            # 启动隧道（如果需要）
            if not self.is_simulator and not self.use_usb:
                if int(self.max_version) >= 17:
                    if not self._start_tunnel():
                        logger.warning("隧道启动失败，但将继续尝试连接")

            # 设置端口转发（对于真机）
            if not self.is_simulator and not self.use_usb:
                # if not self._setup_port_forwarding():
                logger.warning("请确认已经设置端口转发!")

            # 启动WDA
            # if not self._start_wda():
            #     logger.error("启动WDA失败")
            #     return False

            # 初始化WDA客户端
            if not self._init_wda():
                logger.error("初始化WDA客户端失败")
                return False

            return True
        except Exception as e:
            logger.error(f"启动iOS自动化驱动失败: {e}")
            return False

    def stop(self):
        """
        停止iOS自动化驱动并释放资源
        """
        logger.info("正在停止iOS自动化驱动...")

        # 释放wdapy资源
        if self.wda:
            try:
                self.wda = None
            except Exception as e:
                logger.warning(f"释放wdapy资源失败: {e}")

        # 停止WDA进程
        if hasattr(self, '_wda_process') and self._wda_process:
            try:
                self._wda_process.terminate()
                self._wda_process.wait(timeout=5)
                self._wda_process = None
            except Exception as e:
                logger.warning(f"停止WDA进程失败: {e}")

        logger.info("iOS自动化驱动已停止")

    def restart(self):
        """
        重启iOS自动化驱动
        """
        logger.info("正在重启iOS自动化驱动...")
        self.stop()
        time.sleep(1)
        self.start()
        logger.info("iOS自动化驱动已重启")

    def _find_element(self, selector: str, timeout: float = 10.0):
        """
        查找元素，支持多种定位策略
        
        Args:
            selector: 元素选择器，可以是多种格式：
                     - 直接传入定位条件，如 name=value 或 id=value
                     - 多个属性组合，如 "name=按钮,visible=true,enabled=true"
                     - 以 // 开头的 XPath 表达式
                     - 以 - 开头的 NSPredicate 表达式
                     - 以 **/ 开头的 ClassChain 表达式
                     - 如果没有特殊前缀，默认按 name=selector 查找
            timeout: 等待超时时间，单位秒
            
        Returns:
            element: 元素对象
            
        Raises:
            Exception: 如果未找到元素
        """
        import re

        # 获取session对象
        session = self.wda.session()

        # 检查是否是XPath、Predicate或ClassChain
        if selector.startswith('//'):
            # XPath表达式
            element = session(xpath=selector)
        elif selector.startswith('-'):
            # Predicate表达式 (通常以-开头)
            element = session(predicate=selector)
        elif selector.startswith('**/'):
            # ClassChain表达式
            element = session(classChain=selector)
        else:
            # 使用正则表达式匹配属性=值的模式
            pattern = r'([^=]+)=([^,]+)(?:,|$)'
            matches = re.findall(pattern, selector)

            if len(matches) > 0:
                # 解析属性和值
                attrs = {}
                for key, value in matches:
                    key = key.strip().lower()
                    value = value.strip()

                    # 如果值被引号包围，则移除引号
                    if (value.startswith('"') and value.endswith('"')) or \
                            (value.startswith("'") and value.endswith("'")):
                        value = value[1:-1]

                    # 处理布尔值
                    if value.lower() in ('true', 'false') and key not in ('xpath', 'predicate', 'classchain'):
                        value = (value.lower() == 'true')

                    # 根据属性类型添加到查询条件
                    if key == 'name':
                        attrs['name'] = value
                    elif key == 'id':
                        attrs['id'] = value
                    elif key == 'classname' or key == 'class':
                        attrs['className'] = value
                    elif key == 'label':
                        attrs['label'] = value
                    elif key == 'value':
                        attrs['value'] = value
                    elif key == 'visible':
                        attrs['visible'] = value
                    elif key == 'enabled':
                        attrs['enabled'] = value
                    elif key == 'xpath':
                        attrs['xpath'] = value
                    elif key == 'predicate':
                        attrs['predicate'] = value
                    elif key == 'classchain':
                        attrs['classChain'] = value

                # 使用组合条件查找元素
                element = session(**attrs)
            else:
                # 如果没有特殊前缀，默认按name查找
                element = session(name=selector)

        # 等待元素出现
        start_time = time.time()
        while time.time() - start_time < timeout:
            if element.exists:
                return element
            time.sleep(0.5)

        # 超时未找到元素
        raise Exception(f"未找到元素: {selector}")

    def click(self, selector: str, timeout: float = 10.0):
        """
        点击元素
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 等待超时时间，单位秒
            
        Raises:
            Exception: 如果未找到元素或点击失败
        """
        try:
            self._find_element(selector, timeout).tap()
            logger.info(f"点击元素: {selector}")
        except Exception as e:
            logger.error(f"点击元素失败: {selector}, 错误: {e}")
            raise Exception(f"点击元素失败: {selector}")

    def input_text(self, selector: str, text: str, timeout: float = 10.0):
        """
        在元素中输入文本
        
        Args:
            selector: 元素选择器，支持多种定位策略
            text: 要输入的文本
            timeout: 等待超时时间，单位秒
            
        Raises:
            Exception: 如果未找到元素或输入失败
        """
        try:
            element = self._find_element(selector, timeout)
            element.clear_text()
            element.set_text(text)
            logger.info(f"输入文本: {selector}, 文本: {text}")
        except Exception as e:
            logger.error(f"输入文本失败: {selector}, 文本: {text}, 错误: {e}")
            raise Exception(f"输入文本失败: {selector}")

    def clear_text(self, selector: str, timeout: float = 10.0):
        """
        清除元素中的文本
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 等待超时时间，单位秒
            
        Raises:
            Exception: 如果未找到元素或清除失败
        """
        try:
            self._find_element(selector, timeout).clear_text()
            logger.info(f"清除文本: {selector}")
        except Exception as e:
            logger.error(f"清除文本失败: {selector}, 错误: {e}")
            raise Exception(f"清除文本失败: {selector}")

    def get_text(self, selector: str, timeout: float = 10.0):
        """
        获取元素文本
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 等待超时时间，单位秒
            
        Returns:
            str: 元素文本
            
        Raises:
            Exception: 如果未找到元素或获取失败
        """
        try:
            element = self._find_element(selector, timeout)
            # facebook-wda中，文本值通常存储在text属性中
            text = element.text
            logger.info(f"获取文本: {selector}, 文本: {text}")
            return text
        except Exception as e:
            logger.error(f"获取文本失败: {selector}, 错误: {e}")
            raise Exception(f"获取文本失败: {selector}")

    def wait_for_element(self, selector, timeout=30, interval=1.0):
        """
        等待元素出现
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 超时时间，单位秒
            interval: 检查间隔，单位秒
            
        Returns:
            element: 元素对象，如果超时则返回None
        """
        try:
            logger.info(f"等待元素出现: {selector}, 超时时间: {timeout}秒")

            start_time = time.time()
            while time.time() - start_time < timeout:
                try:
                    element = self._find_element(selector, 0.1)  # 使用较短的超时时间进行尝试
                    if element.exists:
                        logger.info(f"元素已出现: {selector}, 耗时: {time.time() - start_time:.2f}秒")
                        return element
                except Exception:
                    pass

                time.sleep(interval)

            logger.warning(f"等待元素出现超时: {selector}")
            return None
        except Exception as e:
            logger.error(f"等待元素出现异常: {e}")
            return None

    def wait_for_element_to_disappear(self, selector, timeout=30, interval=1.0):
        """
        等待元素消失
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 超时时间，单位秒
            interval: 检查间隔，单位秒
            
        Returns:
            bool: 是否成功等待元素消失
        """
        try:
            logger.info(f"等待元素消失: {selector}, 超时时间: {timeout}秒")

            start_time = time.time()
            while time.time() - start_time < timeout:
                try:
                    element = self._find_element(selector, 0.1)  # 使用较短的超时时间进行尝试
                    if not element.exists:
                        logger.info(f"元素已消失: {selector}, 耗时: {time.time() - start_time:.2f}秒")
                        return True
                except Exception:
                    # 如果未找到元素，表示元素已消失
                    logger.info(f"元素已消失: {selector}, 耗时: {time.time() - start_time:.2f}秒")
                    return True

                time.sleep(interval)

            logger.warning(f"等待元素消失超时: {selector}")
            return False
        except Exception as e:
            logger.error(f"等待元素消失异常: {e}")
            return False

    def long_click(self, selector: str, duration: float = 1.0, timeout: float = 10.0):
        """
        长按元素
        
        Args:
            selector: 元素选择器，支持多种定位策略
            duration: 长按持续时间，单位秒
            timeout: 等待超时时间，单位秒
            
        Raises:
            Exception: 如果未找到元素或长按失败
        """
        try:
            element = self._find_element(selector, timeout)
            element.tap_hold(duration)
            logger.info(f"长按元素: {selector}, 时间: {duration}秒")
        except Exception as e:
            logger.error(f"长按元素失败: {selector}, 错误: {e}")
            raise Exception(f"长按元素失败: {selector}")

    def swipe(self, start_x: float, start_y: float, end_x: float, end_y: float, duration: float = 0.5):
        """
        滑动操作
        
        Args:
            start_x: 起始点x坐标，可以是百分比(0-1)或绝对坐标
            start_y: 起始点y坐标，可以是百分比(0-1)或绝对坐标
            end_x: 结束点x坐标，可以是百分比(0-1)或绝对坐标
            end_y: 结束点y坐标，可以是百分比(0-1)或绝对坐标
            duration: 滑动持续时间，单位秒
        """
        try:
            # 获取屏幕尺寸
            size = self.get_window_size()
            width, height = size['width'], size['height']

            # 如果是百分比坐标，转换为绝对坐标
            if 0 <= start_x <= 1:
                start_x_abs = int(start_x * width)
            else:
                start_x_abs = int(start_x)

            if 0 <= start_y <= 1:
                start_y_abs = int(start_y * height)
            else:
                start_y_abs = int(start_y)

            if 0 <= end_x <= 1:
                end_x_abs = int(end_x * width)
            else:
                end_x_abs = int(end_x)

            if 0 <= end_y <= 1:
                end_y_abs = int(end_y * height)
            else:
                end_y_abs = int(end_y)

            # 使用facebook-wda的swipe方法
            self.wda.session().swipe(start_x_abs, start_y_abs, end_x_abs, end_y_abs, duration)
            logger.info(f"滑动: 从 ({start_x}, {start_y}) 到 ({end_x}, {end_y}), 时间: {duration}秒")
        except Exception as e:
            logger.error(f"滑动失败: 从 ({start_x}, {start_y}) 到 ({end_x}, {end_y}), 错误: {e}")
            raise Exception(f"滑动失败: 从 ({start_x}, {start_y}) 到 ({end_x}, {end_y})")

    def swipe_up(self):
        """
        向上滑动
        
        Args:
            distance: 滑动距离 (0-1)
            duration: 滑动时间，单位秒
            
        Raises:
            Exception: 如果操作失败
        """
        try:
            # 获取屏幕尺寸
            self.wda.swipe_up()
            logger.info(f"向上滑动")
        except Exception as e:
            logger.error(f"向上滑动失败,错误: {e}")
            raise Exception(f"向上滑动失败")

    def swipe_down(self):
        """
        向下滑动
        
        Args:
            distance: 滑动距离 (0-1)
            duration: 滑动时间，单位秒
            
        Raises:
            Exception: 如果操作失败
        """
        try:
            # 获取屏幕尺寸
            self.swipe_down()
            logger.info(f"向下滑动")
        except Exception as e:
            logger.error(f"向下滑动失败, 错误: {e}")
            raise Exception(f"向下滑动失败")

    def swipe_left(self):
        """
        向左滑动
        
        Args:
            distance: 滑动距离 (0-1)
            duration: 滑动时间，单位秒
            
        Raises:
            Exception: 如果操作失败
        """
        try:
            self.wda.swipe_left()

            logger.info(f"向左滑动")
        except Exception as e:
            logger.error(f"向左滑动失败, 错误: {e}")
            raise Exception(f"向左滑动失败")

    def swipe_right(self):
        """
        向右滑动
        
        """
        try:
            self.wda.swipe_right()
            logger.info(f"向右滑动")
        except Exception as e:
            logger.error(f"向右滑动失败, 错误: {e}")
            raise Exception(f"向右滑动失败")

    def get_window_size(self):
        """
        获取屏幕尺寸
        
        Returns:
            dict: 屏幕尺寸，包含width和height
        """
        try:
            size = self.wda.window_size()
            return size.width, size.height
        except Exception as e:
            logger.error(f"获取屏幕尺寸失败: {e}")
            raise Exception("获取屏幕尺寸失败")

    def get_screen_resolution(self):
        """获取手机分辨率"""
        try:
            width, height = self.wda.window_size()
            scale = self.wda.scale
            return width * scale, height * scale
        except Exception as e:
            logger.error(f"获取屏幕尺寸失败: {e}")
            raise Exception("获取屏幕尺寸失败")

    def take_screenshot(self, pic_format: str = "opencv"):
        """
        截取屏幕截图
        
        Args:
            :param pic_format:
            
        Raises:
            Exception: 如果操作失败

        """
        try:
            # 使用go-ios的截图功能,格式: ios screenshot [options] [--output=<outfile>]
            temp_file = tempfile.mktemp(suffix='.png')
            cmd = ["ios", "screenshot", "--output", temp_file]
            if self.device_id:
                cmd.append(f"--udid={self.device_id}")

            result = subprocess.run(cmd, capture_output=True, text=True)
            if result.returncode == 0 and os.path.exists(temp_file):
                logger.info(f"截图已保存到: {temp_file}")

                try:

                    if pic_format == "opencv":
                        p_d = imgcv.imread(str(temp_file))
                    elif pic_format == "pillow":
                        from PIL import Image
                        p_d = Image.open(str(temp_file))
                    else:
                        raise ValueError("format 参数错误，仅支持 'opencv' 或 'pillow'")
                    return p_d
                except Exception as e:
                    raise e
                finally:
                    os.remove(temp_file)
            else:
                raise Exception(f"截图失败: {result.stderr}")
        except Exception as e:
            logger.error(f"截图失败: {e}")
            raise Exception(f"截图失败: {e}")

    def get_device_info(self):
        """
        获取设备信息
        
        Returns:
            Dict: 设备信息
        """
        try:
            # 使用go-ios获取设备信息
            info = self._get_device_info()

            # 添加屏幕尺寸信息
            if self.screen_size:
                info["screen_size"] = self.screen_size

            logger.info(f"获取设备信息: {info}")
            return info
        except Exception as e:
            logger.error(f"获取设备信息失败: {e}")
            raise Exception(f"获取设备信息失败: {e}")

    def alert_exists(self, timeout: float = 1.0):
        """
        检查是否存在弹窗
        
        Args:
            timeout: 等待超时时间，单位秒
            
        Returns:
            bool: 是否存在弹窗
        """
        try:
            # 设置较短的超时时间，避免长时间等待
            start_time = time.time()
            while time.time() - start_time < timeout:
                try:
                    return self.wda.alert.exists
                except Exception as e:
                    if "no such alert" in str(e):
                        time.sleep(0.1)
                        continue
                    raise
            return False
        except Exception as e:
            logger.error(f"检查弹窗失败: {e}")
            return False

    def alert_accept(self):
        """
        接受弹窗
        
        Returns:
            bool: 是否成功接受弹窗
        """
        try:
            # 先检查弹窗是否存在
            if not self.alert_exists():
                logger.info("没有弹窗存在，无需接受")
                return True
                
            self.wda.alert.accept()
            logger.info("接受弹窗")
            return True
        except Exception as e:
            # 检查是否是 no such alert 错误
            if "no such alert" in str(e):
                logger.warning("尝试接受不存在的弹窗")
                return True
            logger.error(f"接受弹窗失败: {e}")
            return False

    def alert_dismiss(self):
        """
        拒绝弹窗
        
        Returns:
            bool: 是否成功拒绝弹窗
        """
        try:
            # 先检查弹窗是否存在
            if not self.alert_exists():
                logger.info("没有弹窗存在，无需拒绝")
                return True
                
            self.wda.alert.dismiss()
            logger.info("拒绝弹窗")
            return True
        except Exception as e:
            # 检查是否是 no such alert 错误
            if "no such alert" in str(e):
                logger.warning("尝试拒绝不存在的弹窗")
                return True
            logger.error(f"拒绝弹窗失败: {e}")
            return False

    def alert_buttons(self):
        """
        获取弹窗按钮
        
        Returns:
            list: 弹窗按钮列表，如果没有弹窗则返回空列表
        """
        try:
            # 先检查弹窗是否存在
            if not self.alert_exists():
                logger.info("没有弹窗存在，无法获取按钮")
                return []
                
            buttons = self.wda.alert.buttons
            logger.info(f"弹窗按钮: {buttons}")
            return buttons
        except Exception as e:
            # 检查是否是 no such alert 错误
            if "no such alert" in str(e):
                logger.warning("尝试获取不存在的弹窗按钮")
                return []
            logger.error(f"获取弹窗按钮失败: {e}")
            return []

    def alert_click(self, button_name: str):
        """
        点击弹窗按钮
        
        Args:
            button_name: 按钮名称
            
        Returns:
            bool: 是否成功点击弹窗按钮
        """
        try:
            # 先检查弹窗是否存在
            if not self.alert_exists():
                logger.info(f"没有弹窗存在，无法点击按钮: {button_name}")
                return False
                
            self.wda.alert.click(button_name)
            logger.info(f"点击弹窗按钮: {button_name}")
            return True
        except Exception as e:
            # 检查是否是 no such alert 错误
            if "no such alert" in str(e):
                logger.warning(f"尝试点击不存在的弹窗按钮: {button_name}")
                return False
            logger.error(f"点击弹窗按钮失败: {button_name}, 错误: {e}")
            return False

    def alert_text(self):
        """
        获取弹窗文本
        
        Returns:
            str: 弹窗文本，如果没有弹窗则返回空字符串
        """
        try:
            # 先检查弹窗是否存在
            if not self.alert_exists():
                logger.info("没有弹窗存在，无法获取文本")
                return ""
                
            text = self.wda.alert.text
            logger.info(f"弹窗文本: {text}")
            return text
        except Exception as e:
            # 检查是否是 no such alert 错误
            if "no such alert" in str(e):
                logger.warning("尝试获取不存在的弹窗文本")
                return ""
            logger.error(f"获取弹窗文本失败: {e}")
            return ""

    def app_launch(self, bundle_id: str = None, wait_for_quiescence: bool = False):
        """
        启动应用
        
        Args:
            bundle_id: 应用的bundle id
            wait_for_quiescence: 是否等待应用稳定
            
        Returns:
            bool: 是否成功启动应用
        """
        try:
            if bundle_id is None:
                bundle_id = self.app_bundle_id
            self.wda.app_start(bundle_id, wait_for_quiescence=wait_for_quiescence)
            logger.info(f"启动应用: {bundle_id}")
            return True
        except Exception as e:
            logger.error(f"启动应用失败: {bundle_id}, 错误: {e}")
            return False

    def app_terminate(self, bundle_id: str=None):
        """
        关闭应用
        
        Args:
            bundle_id: 应用的bundle id
            
        Returns:
            bool: 是否成功关闭应用
        """
        try:
            if bundle_id is None:
                bundle_id = self.app_bundle_id
            self.wda.app_terminate(bundle_id)
            logger.info(f"关闭应用: {bundle_id}")
            return True
        except Exception as e:
            logger.error(f"关闭应用失败: {bundle_id}, 错误: {e}")
            return False

    def app_current(self):
        """
        获取当前运行的应用信息
        
        Returns:
            dict: 当前应用信息，包含pid、name和bundleId
        """
        try:
            app_info = self.wda.app_current()
            logger.info(f"当前应用: {app_info}")
            return app_info
        except Exception as e:
            logger.error(f"获取当前应用失败: {e}")
            return {}

    def app_state(self, bundle_id: str=None):
        """
        获取应用状态
        
        Args:
            bundle_id: 应用的bundle id
            
        Returns:
            int: 应用状态，1(未运行) 2(后台运行) 3(前台运行)
        """
        try:
            if bundle_id is None:
                bundle_id = self.app_bundle_id
            state = self.wda.app_state(bundle_id)
            logger.info(f"应用状态: {bundle_id}, 状态: {state}")
            return state
        except Exception as e:
            logger.error(f"获取应用状态失败: {bundle_id}, 错误: {e}")
            return -1

    def home(self):
        """
        按下Home键
        
        Returns:
            bool: 是否成功按下Home键
        """
        try:
            self.wda.home()
            logger.info("按下Home键")
            return True
        except Exception as e:
            logger.error(f"按下Home键失败: {e}")
            return False

    def lock(self):
        """
        锁定设备
        
        Returns:
            bool: 是否成功锁定设备
        """
        try:
            self.wda.lock()
            logger.info("锁定设备")
            return True
        except Exception as e:
            logger.error(f"锁定设备失败: {e}")
            return False

    def unlock(self):
        """
        解锁设备
        
        Returns:
            bool: 是否成功解锁设备
        """
        try:
            self.wda.unlock()
            logger.info("解锁设备")
            return True
        except Exception as e:
            logger.error(f"解锁设备失败: {e}")
            return False

    def is_locked(self):
        """
        检查设备是否锁定
        
        Returns:
            bool: 设备是否锁定
        """
        try:
            locked = self.wda.locked()
            logger.info(f"设备锁定状态: {locked}")
            return locked
        except Exception as e:
            logger.error(f"检查设备锁定状态失败: {e}")
            return False

    def deactivate(self, duration: float = 3.0):
        """
        将应用置于后台一段时间后再激活
        
        Args:
            duration: 后台停留时间，单位秒
            
        Returns:
            bool: 是否成功执行
        """
        try:
            self.wda.deactivate(duration)
            logger.info(f"应用置于后台: {duration}秒")
            return True
        except Exception as e:
            logger.error(f"应用置于后台失败: {e}")
            return False

    def open_url(self, url: str):
        """
        打开URL
        
        Args:
            url: 要打开的URL
            
        Returns:
            bool: 是否成功打开URL
        """
        try:
            self.wda.open_url(url)
            logger.info(f"打开URL: {url}")
            return True
        except Exception as e:
            logger.error(f"打开URL失败: {url}, 错误: {e}")
            return False

    def _get_screen_size(self):
        """
        获取屏幕尺寸
        
        Returns:
            dict: 包含width和height的字典
        """
        try:
            # 使用GoIOS截图并获取尺寸
            temp_file = tempfile.mktemp(suffix='.png')
            try:
                # 使用GoIOS截图
                GoIOS.take_screenshot(temp_file, self.device_id)

                # 使用PIL获取图像尺寸
                try:
                    from PIL import Image
                    with Image.open(temp_file) as img:
                        width, height = img.size
                    return {"width": width, "height": height}
                except ImportError:
                    logger.warning("未安装PIL，无法获取屏幕尺寸")
                    return {"width": 0, "height": 0}
            finally:
                # 确保临时文件被删除
                if os.path.exists(temp_file):
                    os.remove(temp_file)
        except Exception as e:
            logger.error(f"获取屏幕尺寸失败: {e}")
            return {"width": 0, "height": 0}

    def __call__(self, *args, **kwargs):
        return self.wda(*args, **kwargs)

    def get_available_simulators(self):
        """
        获取可用的模拟器列表
        
        Returns:
            list: 模拟器列表，包含所有模拟器（不仅限于已启动的）
        """
        try:
            sim_data = Simulator.list_devices()
            simulators = []

            # 提取所有iOS设备
            for runtime, devices in sim_data.get("devices", {}).items():
                for device in devices:
                    simulators.append({
                        "udid": device.get("udid"),
                        "name": device.get("name"),
                        "state": device.get("state"),
                        "runtime": runtime,
                        "isAvailable": device.get("isAvailable", True)
                    })

            return simulators
        except Exception as e:
            logger.error(f"获取模拟器列表失败: {e}")
            return []

    def get_simulator_device_types(self):
        """
        获取可用的模拟器设备类型
        
        Returns:
            list: 设备类型列表
        """
        try:
            device_types = Simulator.list_device_types()
            return device_types.get("devicetypes", [])
        except Exception as e:
            logger.error(f"获取模拟器设备类型失败: {e}")
            return []

    def boot_simulator(self, udid=None):
        """
        启动模拟器
        
        Args:
            udid: 模拟器UDID，如果为None则使用当前设备ID
            
        Returns:
            bool: 是否成功
        """
        try:
            udid = udid or self.device_id
            if not udid:
                logger.error("未指定模拟器UDID")
                return False

            Simulator.boot_device(udid)
            logger.info(f"启动模拟器成功: {udid}")
            return True
        except Exception as e:
            logger.error(f"启动模拟器失败: {e}")
            return False

    def shutdown_simulator(self, udid=None):
        """
        关闭模拟器
        
        Args:
            udid: 模拟器UDID，如果为None则使用当前设备ID
            
        Returns:
            bool: 是否成功
        """
        try:
            udid = udid or self.device_id
            if not udid:
                logger.error("未指定模拟器UDID")
                return False

            Simulator.shutdown_device(udid)
            logger.info(f"关闭模拟器成功: {udid}")
            return True
        except Exception as e:
            logger.error(f"关闭模拟器失败: {e}")
            return False

    def install_app_to_simulator(self, app_path, udid=None):
        """
        在模拟器上安装应用
        
        Args:
            app_path: 应用路径
            udid: 模拟器UDID，如果为None则使用当前设备ID
            
        Returns:
            bool: 是否成功
        """
        try:
            udid = udid or self.device_id
            if not udid:
                logger.error("未指定模拟器UDID")
                return False

            Simulator.install_app(udid, app_path)
            logger.info(f"安装应用成功: {app_path}")
            return True
        except Exception as e:
            logger.error(f"安装应用失败: {e}")
            return False

    def uninstall_app_from_simulator(self, bundle_id, udid=None):
        """
        从模拟器卸载应用
        
        Args:
            bundle_id: 应用Bundle ID
            udid: 模拟器UDID，如果为None则使用当前设备ID
            
        Returns:
            bool: 是否成功
        """
        try:
            udid = udid or self.device_id
            if not udid:
                logger.error("未指定模拟器UDID")
                return False

            Simulator.uninstall_app(udid, bundle_id)
            logger.info(f"卸载应用成功: {bundle_id}")
            return True
        except Exception as e:
            logger.error(f"卸载应用失败: {e}")
            return False

    def launch_app_in_simulator(self, bundle_id, udid=None):
        """
        在模拟器上启动应用
        
        Args:
            bundle_id: 应用Bundle ID
            udid: 模拟器UDID，如果为None则使用当前设备ID
            
        Returns:
            bool: 是否成功
        """
        try:
            udid = udid or self.device_id
            if not udid:
                logger.error("未指定模拟器UDID")
                return False

            Simulator.launch_app(udid, bundle_id)
            logger.info(f"启动应用成功: {bundle_id}")
            return True
        except Exception as e:
            logger.error(f"启动应用失败: {e}")
            return False

    def take_simulator_screenshot(self, output_path, udid=None):
        """
        模拟器截图
        
        Args:
            output_path: 输出路径
            udid: 模拟器UDID，如果为None则使用当前设备ID
            
        Returns:
            bool: 是否成功
        """
        try:
            udid = udid or self.device_id
            if not udid:
                logger.error("未指定模拟器UDID")
                return False

            Simulator.take_screenshot(udid, output_path)
            logger.info(f"截图成功: {output_path}")
            return True
        except Exception as e:
            logger.error(f"截图失败: {e}")
            return False

    def get_element_info(self, selector: str, timeout: float = 10.0):
        """
        获取元素的详细信息
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 等待超时时间，单位秒
            
        Returns:
            dict: 元素信息，包括以下属性：
                - visible: 是否可见
                - enabled: 是否启用
                - displayed: 是否显示
                - accessible: 是否可访问
                - text: 元素文本
                - label: 元素标签
                - value: 元素值
                - className: 元素类名
                - name: 元素名称
                - exists: 元素是否存在
                - bounds: 元素边界 (x, y, width, height)
                
        Raises:
            Exception: 如果未找到元素或获取失败
        """
        try:
            element = self._find_element(selector, timeout)

            # 获取元素的各种属性
            info = {
                'visible': element.visible,
                'enabled': element.enabled,
                'displayed': element.displayed,
                'accessible': element.accessible,
                'text': element.text,
                'label': element.label,
                'value': element.value,
                'className': element.className,
                'name': element.name,
                'exists': element.exists
            }

            # 获取元素边界
            bounds = element.bounds
            info['bounds'] = {
                'x': bounds.x,
                'y': bounds.y,
                'width': bounds.width,
                'height': bounds.height
            }

            logger.info(f"获取元素信息: {selector}")
            return info
        except Exception as e:
            logger.error(f"获取元素信息失败: {selector}, 错误: {e}")
            raise Exception(f"获取元素信息失败: {selector}")

    def is_element_visible(self, selector: str, timeout: float = 10.0):
        """
        判断元素是否可见
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 等待超时时间，单位秒
            
        Returns:
            bool: 元素是否可见
        """
        try:
            element = self._find_element(selector, timeout)
            return element.visible
        except Exception:
            return False

    def is_element_enabled(self, selector: str, timeout: float = 10.0):
        """
        判断元素是否启用（可交互）
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 等待超时时间，单位秒
            
        Returns:
            bool: 元素是否启用
        """
        try:
            element = self._find_element(selector, timeout)
            return element.enabled
        except Exception:
            return False

    def is_element_displayed(self, selector: str, timeout: float = 10.0):
        """
        判断元素是否显示（可见且在屏幕上）
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 等待超时时间，单位秒
            
        Returns:
            bool: 元素是否显示
        """
        try:
            element = self._find_element(selector, timeout)
            return element.displayed
        except Exception:
            return False

    def is_element_accessible(self, selector: str, timeout: float = 10.0):
        """
        判断元素是否可访问（可用于辅助功能）
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 等待超时时间，单位秒
            
        Returns:
            bool: 元素是否可访问
        """
        try:
            element = self._find_element(selector, timeout)
            return element.accessible
        except Exception:
            return False

    def get_element_bounds(self, selector: str, timeout: float = 10.0):
        """
        获取元素的边界信息
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 等待超时时间，单位秒
            
        Returns:
            dict: 元素边界信息，包含 x, y, width, height
            
        Raises:
            Exception: 如果未找到元素或获取失败
        """
        try:
            element = self._find_element(selector, timeout)
            bounds = element.bounds
            return {
                'x': bounds.x,
                'y': bounds.y,
                'width': bounds.width,
                'height': bounds.height
            }
        except Exception as e:
            logger.error(f"获取元素边界失败: {selector}, 错误: {e}")
            raise Exception(f"获取元素边界失败: {selector}")

    def element_exists(self, selector: str, timeout: float = 1.0):
        """
        判断元素是否存在
        
        Args:
            selector: 元素选择器，支持多种定位策略
            timeout: 等待超时时间，单位秒
            
        Returns:
            bool: 元素是否存在
        """
        try:
            self._find_element(selector, timeout)
            return True
        except Exception:
            return False

    def get_driver(self):
        return self.webview.driver


if __name__ == '__main__':
    d = IOSDriver(is_simulator=False, use_usb=True, use_appium_webview=False)

    print(d.wda.source('json'))
    # d(name='26-04-2025').click()
